﻿using System;
using System.Collections.Generic;

public class PhotonMap
{
    public List<Photon> photons;
    public List<int> photonsCountBySource;
    public TreeKD tree;
    public List<Photon> nearestPhotons;
    public float d;
    public int count;
    public int count2;
	public PhotonMap(int lightsCount)
	{
        count = 0;
        count2 = 0;
        photons = new List<Photon>();
        photonsCountBySource = new List<int>(lightsCount);
        nearestPhotons = new List<Photon>();
        tree = new TreeKD();
        for(int i=0;i<lightsCount;i++)
        {
            photonsCountBySource.Add(0);
        }
	}
    public bool add(Photon p)
    {
        //if (photons.Count < photons.Capacity)
        //{
            photons.Add(p);
            photonsCountBySource[p.sourceId]++;
            return true;
        //}
        //else
        //{
        //    return false;
        //}
    }
    public void scaleEnergy()
    {
        foreach (Photon p in photons)
        {
            p.energy /= photonsCountBySource[p.sourceId];
            //p.energy /= 5000;
        }
    }
    public List<Photon> findNearest(Vector3 point, float range, int requestedNumber)
    {
        List<Photon> ret = new List<Photon>();
        foreach (Photon p in photons)
        {
            if((p.position-point).length()<=range)
            {
                ret.Add(p);
            }
            if (ret.Count >= requestedNumber)
                return ret;
        }
        return ret;
    }
    public void findPhotons(int pos, Vector3 point, int requestedNumber)
    {
        /*float q = (point - tree.nodes[pos - 1].position).length();
        if ((q) <= (d))
        {
            tree.nodes[pos - 1].d = q;
            nearestPhotons.Add(tree.nodes[pos - 1]);
        }
        if ((2 * pos + 1) < tree.nodes.Length)
        {
            float dist;
            if (tree.nodes[pos - 1].flag == 0)
                dist = (point.x - tree.nodes[pos - 1].position.x);
            else if (tree.nodes[pos - 1].flag == 1)
                dist = (point.y - tree.nodes[pos - 1].position.y);
            else
                dist = (point.z - tree.nodes[pos - 1].position.z);
            if (dist < 0)
            {
                findPhotons(2 * pos, point, requestedNumber);
                if ((dist * dist) <= (d * d))
                {
                    findPhotons(2 * pos + 1, point, requestedNumber);
                }
            }
            else
            {
                findPhotons(2 * pos + 1, point, requestedNumber);
                if ((dist * dist) <= (d * d))
                {
                    findPhotons(2 * pos, point, requestedNumber);
                }
            }
        }*/

        float q = (point - tree.nodes[pos - 1].position).length();
        if ((q) < (d))
        {
            tree.nodes[pos - 1].d = (point - tree.nodes[pos - 1].position).length();
            nearestPhotons.Add(tree.nodes[pos - 1]);
            if (nearestPhotons.Count == requestedNumber)
            {
                nearestPhotons.Sort(comparer);
                //return;
                //nearestPhotons.RemoveAt(0);
                //d = (point - nearestPhotons[0].position).length();
                nearestPhotons.RemoveAt(nearestPhotons.Count - 1);
                d = (point - nearestPhotons[nearestPhotons.Count - 1].position).length();
            }
        }

        if ((2 * pos + 1) < photonsCountBySource[0])
        {
            float dist;
            if (tree.nodes[pos - 1].flag == 0)
                dist = (point.x - tree.nodes[pos - 1].position.x);
            else if (tree.nodes[pos - 1].flag == 1)
                dist = (point.y - tree.nodes[pos - 1].position.y);
            else
                dist = (point.z - tree.nodes[pos - 1].position.z);
            if (dist < 0)
            {
                findPhotons(2 * pos, point, requestedNumber);
                if ((dist * dist) < (d * d))
                {
                    findPhotons(2 * pos + 1, point, requestedNumber);
                }
            }
            else
            {
                findPhotons(2 * pos + 1, point, requestedNumber);
                if ((dist * dist) < (d * d))
                {
                    findPhotons(2 * pos, point, requestedNumber);
                }
            }
        }
        //float q = (point - tree.nodes[pos - 1].position).length();
        //if ((q*q) < (d*d))
        //{
        //    tree.nodes[pos - 1].d = (point - tree.nodes[pos - 1].position).length();
        //    nearestPhotons.Add(tree.nodes[pos - 1]);
        //    if (nearestPhotons.Count == requestedNumber)
        //    {
        //        nearestPhotons.Sort(comparer);
        //        //return;
        //        nearestPhotons.RemoveAt(nearestPhotons.Count-1);
        //        d = (point - nearestPhotons[nearestPhotons.Count-1].position).length();
        //    }
        //}
    }
    public void shootPhotons(Vector3 lightPosition, LightIntensity energy, int sourceId, int photonsCount, Scene scene)
    {
        int photonsShot = 0;
        int intersectedPrimitive = -1;
        float x,y,z;
        Random rand = new Random();
        Ray ray;
        while(photonsShot<photonsCount)
        {
            do
            {
                x = (float)((rand.NextDouble() * 2.0)-1.0);
                y = (float)((rand.NextDouble() * 2.0)-1.0);
                z = (float)((rand.NextDouble() * 2.0)-1.0);
            }while((x*x+y*y+z*z)>1);

            Vector3 dir = new Vector3(x, y, z);
            Vector3 nor = new Vector3(0, -1, 0);
            dir.normalize();
            if (dir.dot(nor) < 0)
            {
                dir = -dir;
            }

            ray = new Ray(lightPosition, dir,true);
            Photon photon = new Photon(lightPosition, energy*50, -ray.direction, sourceId);

            for (int k = 0; k < scene.objectsNumber(); k++)
            {
                if (scene.objects[k].intersect(ray))
                    intersectedPrimitive = k;
            }
            if (intersectedPrimitive >= 0)
            {
                tracePhotons(photon, scene, ray, intersectedPrimitive, 3);
                photonsShot++;
            }
            //photonsShot++;
        }
        //po tej metodzie dla kazdego zrodla swiatla wywolac jeden raz scaleEnergy()
        //scaleEnergy();
        tree = new TreeKD(this.photonsCountBySource[0]);
        Console.WriteLine(this.photonsCountBySource[0]);
    }


    public void shootCausticPhotons(Vector3 lightPosition, Vector3 objectCentre, float objectWidth, LightIntensity energy, int sourceId, int photonsCount, Scene scene)
    {
        int photonsShot = 0;
        int intersectedPrimitive = -1;
        float x, y, z;
        Random rand = new Random();
        Ray ray;
        while (photonsShot < photonsCount)
        {
            do
            {
                x = (float)((rand.NextDouble() * 2.0 * objectWidth) - objectWidth);
                y = (float)((rand.NextDouble() * 2.0 * objectWidth) - objectWidth);
                z = (float)((rand.NextDouble() * 2.0 * objectWidth) - objectWidth);
            } while ((x * x + y * y + z * z) > objectWidth);

            Vector3 dir = new Vector3(objectCentre.x + x, objectCentre.y + y, objectCentre.z + z);

            ray = new Ray(lightPosition, dir, false);
            Photon photon = new Photon(lightPosition, energy*50, -ray.direction, sourceId);

            for (int k = 0; k < scene.objectsNumber(); k++)
            {
                if (scene.objects[k].intersect(ray))
                    intersectedPrimitive = k;
            }
            if (intersectedPrimitive >= 0)
            {
                traceCausticPhotons(photon, scene, ray, intersectedPrimitive, 3);
                photonsShot++;
            }
        }
        //po tej metodzie dla kazdego zrodla swiatla wywolac jeden raz scaleEnergy()
        //scaleEnergy();
        tree = new TreeKD(this.photonsCountBySource[0]);
        Console.WriteLine(this.photonsCountBySource[0]);
    }


    public Photon tracePhotons(Photon photon, Scene scene, Ray ray, int intersectedPrimitive, int reflectionNumber)
    {
        Ray nextRay = new Ray();
        Random rand = new Random();
        float k;
        bool hit = false;
        if (scene.objects[intersectedPrimitive].material.type == (int)Material.Types.diffuse || reflectionNumber <= 0)
        {
            //Photon photonAbsorbed = new Photon(ray.destination, photon.energy, (photon.position - ray.destination).normalize(), photon.sourceId);
            photon.direction = (photon.position-ray.destination).normalize();
            photon.position = ray.destination;
            this.add(photon);
            k = (float)rand.NextDouble();
            if (k < scene.objects[intersectedPrimitive].material.reflectionProb && reflectionNumber > 0)
            {
                double kAvg = (scene.objects[intersectedPrimitive].material.Kd.r+scene.objects[intersectedPrimitive].material.Kd.g+scene.objects[intersectedPrimitive].material.Kd.b)/3;
                photon.energy.r *= (scene.objects[intersectedPrimitive].material.Kd.r / kAvg);
                photon.energy.g *= (scene.objects[intersectedPrimitive].material.Kd.g / kAvg);
                photon.energy.b *= (scene.objects[intersectedPrimitive].material.Kd.b / kAvg);
                float x, y, z;
                do
                {
                    x = (float)((rand.NextDouble() * 2.0) - 1.0);
                    y = (float)((rand.NextDouble() * 2.0) - 1.0);
                    z = (float)((rand.NextDouble() * 2.0) - 1.0);
                } while ((x * x + y * y + z * z) > 1);
                Vector3 dir = new Vector3(x, y, z);
                Vector3 nor = scene.objects[intersectedPrimitive].getNormal(ray.destination);
                dir.normalize();
                if (dir.dot(nor) < 0)
                {
                    dir = -dir;
                }
                nextRay = new Ray(ray.destination, dir, true);
                for (int kk = 0; kk < scene.objectsNumber(); kk++)
                {
                    if (kk != intersectedPrimitive)
                        if (scene.objects[kk].intersect(nextRay))
                        {
                            hit = true;
                            intersectedPrimitive = kk;
                            //kk = scene.objectsNumber();
                        }
                }
                if (hit)
                {
                    photon = tracePhotons(photon, scene, nextRay, intersectedPrimitive, --reflectionNumber);
                }
            }
            //else
            //{
            //}
        }
        else if (reflectionNumber > 0)
        {
            Vector3 L = ray.direction;
            L.normalize();
            if (scene.objects[intersectedPrimitive].material.type == (int)Material.Types.mirror)
            {
                Vector3 N = scene.objects[intersectedPrimitive].getNormal(ray.destination);
                Vector3 R = (L - 2 * N * (N.dot(L)));
                nextRay = new Ray(ray.destination, R, true);
            }
            else if (scene.objects[intersectedPrimitive].material.type == (int)Material.Types.refraction)
            {
                
            }
            hit = false;
            for (int kk = 0; kk < scene.objectsNumber(); kk++)
            {
                if (kk != intersectedPrimitive)
                    if (scene.objects[kk].intersect(nextRay))
                    {
                        hit = true;
                        intersectedPrimitive = kk;
                        //kk = scene.objectsNumber();
                    }
            }
            if (hit)
            {
                photon = tracePhotons(photon, scene, nextRay, intersectedPrimitive, --reflectionNumber);
            }
        }
        //this.add(photon);
        return photon;
    }


    public Photon traceCausticPhotons(Photon photon, Scene scene, Ray ray, int intersectedPrimitive, int reflectionNumber)
    {
        Ray nextRay = new Ray();
        bool hit = false;
        if (scene.objects[intersectedPrimitive].material.type == (int)Material.Types.diffuse || reflectionNumber <= 0)
        {
            photon.direction = (photon.position - ray.destination).normalize();
            photon.position = ray.destination;
            this.add(photon);
        }
        else if (reflectionNumber > 0)
        {
            Vector3 L = ray.direction;
            L.normalize();
            if (scene.objects[intersectedPrimitive].material.type == (int)Material.Types.mirror)
            {
                Vector3 N = scene.objects[intersectedPrimitive].getNormal(ray.destination);
                Vector3 R = (L - 2 * N * (N.dot(L)));
                nextRay = new Ray(ray.destination, R, true);
            }
            else if (scene.objects[intersectedPrimitive].material.type == (int)Material.Types.refraction)
            {

            }
            hit = false;
            for (int kk = 0; kk < scene.objectsNumber(); kk++)
            {
                if (kk != intersectedPrimitive)
                    if (scene.objects[kk].intersect(nextRay))
                    {
                        hit = true;
                        intersectedPrimitive = kk;
                    }
            }
            if (hit)
            {
                photon = traceCausticPhotons(photon, scene, nextRay, intersectedPrimitive, --reflectionNumber);
            }
        }
        return photon;
    }


    public LightIntensity calculateColor(Vector3 hitPoint, float range, int number)
    {
        LightIntensity color = new LightIntensity();
        d = range;
        nearestPhotons.Clear();
        findPhotons(1, hitPoint, number);
        if (nearestPhotons.Count != 0)
        {
            count++;
            //nearestPhotons.Sort(comparer);
            float r = nearestPhotons[nearestPhotons.Count-1].d;
            foreach (Photon p in nearestPhotons)
            {
                color += p.energy;
            }
            color = color / (float)(Math.PI * r * r);
        }
        /*List<Photon> photonList;
        photonList = findNearest(hitPoint,3.0f,4);
        float temp;
        if (photonList.Count != 0)
        {
            float r = (photonList[0].position - hitPoint).length();
            foreach (Photon p in photonList)
            {
                temp = (p.position - hitPoint).length();
                if (temp > r)
                    r = temp;
            }
            foreach (Photon p in photonList)
            {
                color += brdf(hitPoint, p.direction, normal, lookAt, primitive) * p.energy;
            }
            color = color / (float)(Math.PI * r * r);
        }*/
        else
        {
            count2++;
        //    color = new LightIntensity(0.0, 0.8, 0.0);//testy
        }
        return color;
    }
    public LightIntensity brdf(Vector3 hitPoint, Vector3 photonDir, Vector3 N, Vector3 lookAt, Primitive p)
    {
        LightIntensity retColor;
        Vector3 R, L, V;
        float dotNL, dotRV;
        double retR = 0, retG = 0, retB = 0;
        //L = (this.position - hitPoint);
        L = photonDir;
        L.normalize();
        V = (lookAt - hitPoint);
        V.normalize();
        R = (-L + 2 * N * (N.dot(L)));
        R.normalize();
        //L = (this.position - point);
        //L.normalize();
        dotNL = Math.Max(0.0f, N.dot(L));
        dotRV = Math.Max(0.0f, R.dot(V));
        retR = (p.material.Ka.r + p.material.Kd.r * dotNL + p.material.Ks.r * Math.Pow(dotRV, 10));
        retG = (p.material.Ka.g + p.material.Kd.g * dotNL + p.material.Ks.g * Math.Pow(dotRV, 10));
        retB = (p.material.Ka.b + p.material.Kd.b * dotNL + p.material.Ks.b * Math.Pow(dotRV, 10));
        //retR = (dotNL + Math.Pow(dotRV, 10));
        //retG = (dotNL + Math.Pow(dotRV, 10));
        //retB = (dotNL + Math.Pow(dotRV, 10));
        retR = (retR > 1.0 ? 1.0 : (retR < 0.0 ? 0.0 : retR));
        retG = (retG > 1.0 ? 1.0 : (retG < 0.0 ? 0.0 : retG));
        retB = (retB > 1.0 ? 1.0 : (retB < 0.0 ? 0.0 : retB));
        retColor = new LightIntensity(retR, retG, retB);
        retColor = new LightIntensity(1.0, 1.0, 1.0);//testy
        return retColor;
    }
    public static int comparer(Photon p1, Photon p2)
    {
        if (p1.d < p2.d)
            return -1;
        else if (p1.d == p2.d)
            return 0;
        else
            return 1;
    }
}
